﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Net;
using System.IO;

namespace TrackType
{

    /// <summary>
    /// Represents configuration variables for TrackType.NET library
    /// </summary>
    public static class TrackTypeConfig
    {
        static string _url = "http://tracktype.org/~cddb/cddb.cgi";

        static string _user_name = "auto";
        static string _host_name = "localhost";
        static string _client_name = "media_art";
        static string _version = "0.1";

        static string _protocol_version = "6";
        static int _request_timeout = 20000; // ms
        static WebProxy _web_proxy;


        /// <summary>
        /// Gets url of tracktype internet site
        /// </summary>
        public static string Url
        {
            get
            {
                return _url;
            }
        }
        /// <summary>
        /// Gets "hello" banner used to communicate with tracktype site
        /// </summary>
        public static string HelloBanner
        {
            get
            {
                return String.Format("hello={0}+{1}+{2}+{3}",
                    _user_name, _host_name, _client_name, _version);
            }
        }
        /// <summary>
        /// Gets proto banner used to communicate with tracktype site
        /// </summary>
        public static string ProtoBanner
        {
            get
            {
                return String.Format("proto={0}", _protocol_version);
            }
        }
        /// <summary>
        /// Gets timeout in milliseconds which will be used during communication with tractype site
        /// </summary>
        public static int RequestTimeout
        {
            get
            {
                return _request_timeout;
            }
        }
    }

    /// <summary>
    /// Represents request for fetching album headers. Using those headers you can
    /// fetch additional album's information with AlbumRequest
    /// </summary>
    /// 
    /// <example>
    /// <code>
    ///AlbumHeadersResponse response = (AlbumHeadersResponse)request.Execute();
    ///
    ///<![CDATA[if (response != null && response.HasHeaders)]]>
    ///{
    ///    AlbumHeader[] headers = response.AlbumHeaders;
    ///
    ///    //now you can fetch additional album's info using headers
    ///    foreach (AlbumHeader header in headers)
    ///    {
    ///        AlbumRequest detailsRequest = new AlbumRequest(header);
    ///
    ///        AlbumResponse detailsResponse = (AlbumResponse)detailsRequest.Execute();
    ///
    ///        <![CDATA[if (detailsResponse != null && detailsResponse.HasAlbum)]]>
    ///        {
    ///            Album album = detailsResponse.Album;
    ///
    ///            //do whatever you want with album
    ///        }
    ///
    ///    }
    ///}
    /// </code>
    /// </example>
    public class AlbumHeadersRequest : TrackTypeRequestBase
    {
        string _album_name;
        string _artist_name;

        /// <summary>
        /// Gets album name
        /// </summary>
        public string AlbumName
        {
            get
            {
                return _album_name;
            }
        }
        /// <summary>
        /// Gets artist name
        /// </summary>
        public string ArtistName
        {
            get
            {
                return _artist_name;
            }
        }
        /// <summary>
        /// Returns true if ArtistName isn't null or empty
        /// </summary>
        public bool IsArtistSpecified
        {
            get
            {
                return !String.IsNullOrEmpty(_artist_name);
            }
        }

        /// <summary>
        /// Creates new instance using given album name. 
        /// </summary>
        /// <param name="AlbumName"></param>
        public AlbumHeadersRequest(string AlbumName)
            : base(null)
        {
            _album_name = FormatLine(AlbumName);
        }
        /// <summary>
        /// Creates new instance using given album name. 
        /// </summary>
        /// <param name="AlbumName"></param>
        /// <param name="Proxy">WebProxy which will be used to communicate with tracktype site</param>
        public AlbumHeadersRequest(string AlbumName, WebProxy Proxy)
            : base(Proxy)
        {
            _album_name = FormatLine(AlbumName);
        }
        /// <summary>
        /// Creates new instance using given album name and artist name.
        /// </summary>
        /// <param name="AlbumName"></param>
        /// <param name="ArtistName"></param>
        public AlbumHeadersRequest(string AlbumName, string ArtistName)
            : base(null)
        {
            _album_name = FormatLine(AlbumName);
            _artist_name = FormatLine(ArtistName);
        }
        /// <summary>
        /// Creates new instance using given album name and artist name.
        /// </summary>
        /// <param name="AlbumName"></param>
        /// <param name="ArtistName"></param>
        /// <param name="Proxy">WebProxy which will be used to communicate with tracktype site</param>
        public AlbumHeadersRequest(string AlbumName, string ArtistName, WebProxy Proxy)
            : base(Proxy)
        {
            _album_name = AlbumName;
            _artist_name = ArtistName;
        }

        /// <summary>
        /// Executes request and returns AlbumHeadersResponse instance if succeded
        /// </summary>
        /// <returns>AlbumHeadersResponse instance if succeded, null-reference otherwise</returns>
        public override TrackTypeResponseBase Execute()
        {
            AlbumHeadersResponse result = null;

            string command_text = BuildCommand();

            IList<string> responseLines = GetWebResponse(command_text);

            try
            {
                result = new AlbumHeadersResponse(responseLines);
            }
            catch
            {
                result = null;
            }

            return result;
        }           

        //protected members
        /// <summary>
        /// Builds text command which will be passed to tracktype server in order
        /// to get album headers response
        /// </summary>
        /// <returns>Text command</returns>
        protected string BuildCommand()
        {
            string result;

            if (UseCommandWithoutArtist())
                result = BuildAlbumCommand(AlbumName);
            else
                result = BuildAlbumArtistCommand(AlbumName, ArtistName);

            return AddProtocolCharacters(result);
        }
        /// <summary>
        /// Gets value indicating if artist was specified for request
        /// </summary>
        /// <returns>True if artist was specified for request, false otherwise</returns>
        protected bool UseCommandWithoutArtist()
        {
            if (!IsArtistSpecified)
                return true;

            return false;
        }
        /// <summary>
        /// Gets text command for fetching album headers using only album name. E.g. album headers which
        /// match given album name.
        /// </summary>
        /// <param name="AlbumName">Album name</param>
        /// <returns>Text command</returns>
        protected string BuildAlbumCommand(string AlbumName)
        {
            return String.Format("cmd=cddb+album+{0}", AlbumName);
        }
        /// <summary>
        /// Gets text command for fetching album headers using both album name and artists name.
        /// </summary>
        /// <param name="AlbumName">Album name</param>
        /// <param name="ArtistName">Artist name</param>
        /// <returns>Text command</returns>
        protected string BuildAlbumArtistCommand(string AlbumName, string ArtistName)
        {
            return String.Format("cmd=cddb+album+{0}+{1}", ArtistName, AlbumName);
        }
    }

    /// <summary>
    /// Represents request for fetching album details using album header.
    /// You must use AlbumHeadersRequest first to fetch album headers. 
    /// Using headers you can construct AlbumRequest instances in order to fetch albums' details
    /// </summary>
    public class AlbumRequest : TrackTypeRequestBase
    {
        AlbumHeader _header;

        /// <summary>
        /// Creates new instance of request for given album header
        /// </summary>
        /// <param name="header">Album header which will be used to fetch album details</param>
        /// <exception cref="System.NullReferenceException">If header param is null-reference</exception>
        public AlbumRequest(AlbumHeader header)
            : base(null)
        {
            if (header == null)
                throw new NullReferenceException();

            this._header = header;
        }
        /// <summary>
        /// Creates new instance of request for given album header
        /// </summary>
        /// <param name="header">Album header which will be used to fetch album details</param>
        /// <param name="proxy">WebProxy which will be used to communicate with remote server</param>
        /// <exception cref="System.NullReferenceException">If header param is null-reference</exception>
        public AlbumRequest(AlbumHeader header, WebProxy proxy)
            : base(proxy)
        {
            if (header == null)
                throw new NullReferenceException();

            this._header = header;
        }

        /// <summary>
        /// Executes request and fetches album details 
        /// </summary>
        /// <returns>AlbumResponse instance if succeded, null-reference otherwise</returns>
        public override TrackTypeResponseBase Execute()
        {
            string command_text = BuildCommand();

            IList<string> responseLines = GetWebResponse(command_text);

            AlbumResponse result = null;
            try
            {
                result = new AlbumResponse(responseLines);
            }
            catch { }

            return result;
        }

        /// <summary>
        /// Builds text command which will be passed to tracktype site in order to get album details
        /// </summary>
        /// <returns>Text command</returns>
        protected string BuildCommand()
        {
            string cmd_text = String.Format("cmd=cddb+read+{0}+{1}", _header.CathegoryName, _header.AlbumCode);
            return AddProtocolCharacters(cmd_text);
        }
    }

    /// <summary>
    /// Represents response for AlbumHeadersRequest. Don't create instance directrly but
    /// use AlbumHeadersRequest.Execute() method
    /// </summary>
    public class AlbumHeadersResponse : TrackTypeResponseBase
    {
        AlbumHeader[] _album_headers;

        /// <summary>
        /// Gets album headers array recieved from tracktype site
        /// </summary>
        public AlbumHeader[] AlbumHeaders
        {
            get
            {
                if (_album_headers == null)
                    _album_headers = new AlbumHeader[0];

                return _album_headers;
            }
        }
        /// <summary>
        /// Gets value indicating whether tracktype site has returned album headers
        /// </summary>
        public bool HasHeaders
        {
            get
            {
                return (_album_headers != null && _album_headers.Length > 0);
            }
        }

        /// <summary>
        /// Creates new instance by parsing given text lines. Don't create instance directly but
        /// call AlbumHeadersRequest.Execute() method instead
        /// </summary>
        /// <param name="lines"></param>
        public AlbumHeadersResponse(IList<string> lines) :
            base(lines)
        {
            switch (Code)
            {
                case found_exact_match:
                    _album_headers = ParseExactMatch(lines);
                    break;
                case found_inexact_matches:
                    _album_headers = ParseInexactMatch(lines);
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// If tracktype site returned exact match code, parse exact album headers match
        /// </summary>
        /// <param name="lines"></param>
        /// <returns>Array of album headers if succeded, null otherwise</returns>
        protected AlbumHeader[] ParseExactMatch(IList<string> lines)
        {
            AlbumHeader[] result = null;

            int headerOffset = 1;

            if (lines != null && lines.Count > headerOffset)
            {
                AlbumHeader buf = AlbumHeader.Parse(lines[headerOffset]);

                if (buf != null)
                {
                    result = new AlbumHeader[1];
                    result[0] = buf;
                }
            }

            return result;
        }
        /// <summary>
        /// If tracktype site returned inexact match code, parse inexact album headers match
        /// </summary>
        /// <param name="lines"></param>
        /// <returns>Array of album headers if succeded, null otherwise</returns>
        protected AlbumHeader[] ParseInexactMatch(IList<string> lines)
        {
            AlbumHeader[] result = null;

            int headerOffset = 1;

            if (lines != null && lines.Count > headerOffset)
            {
                List<AlbumHeader> items = new List<AlbumHeader>();
                for (int i = headerOffset; i < lines.Count; ++i)
                {
                    AlbumHeader item = AlbumHeader.Parse(lines[i]);

                    if (item != null)
                        items.Add(item);
                }

                result = items.ToArray();
            }

            return result;
        }

    }

    /// <summary>
    /// Represents response for AlbumRequest. Don't create instance directrly but
    /// use AlbumRequest.Execute() method
    /// </summary>
    public class AlbumResponse : TrackTypeResponseBase
    {
        Album _album;

        /// <summary>
        /// Gets album details recieved from tracktype site
        /// </summary>
        public Album Album
        {
            get
            {
                return _album;
            }
        }
        /// <summary>
        /// Gets value indicating whether tracktype has returned album details
        /// </summary>
        public bool HasAlbum
        {
            get
            {
                return (Album != null);
            }
        }

        /// <summary>
        /// Creates new instance of AlbumResponce by parsing given text lines. Don't create instance directrly but
        /// use AlbumRequest.Execute() method
        /// </summary>
        /// <param name="lines"></param>
        public AlbumResponse(IList<string> lines)
            : base(lines)
        {
            _album = Album.Parse(lines);
        }
    }
}
